package com.slfx.idgen.demo.idgen.snowflake;

import com.slfx.idgen.demo.idgen.properties.SnowflakeProperties;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * ©Copyright ©1968-2020 Midea Group,IT
 * FileName: SnowflakeIdUtils
 * Author: zhangjh39
 * Date: 2019-01-02 16:51
 * Description:除了最高位bit标记为不可用以外，其余三组bit占位均可浮动，
 * 看具体的业务需求而定。
 * 默认情况下41bit的时间戳可以支持该算法使用到2082年，
 * 10bit的工作机器id可以支持1023台机器，序列号支持1毫秒产生4095个自增序列id
 * <p>
 * Snowflake – 时间戳
 * 这里时间戳的细度是毫秒级，具体代码如下，建议使用64位linux系统机器，因为有vdso，gettimeofday()在用户态就可以完成操作，减少了进入内核态的损耗。
 * <p>
 * Snowflake – 工作机器id
 * 严格意义上来说这个bit段的使用可以是进程级，机器级的话你可以使用MAC地址来唯一标示工作机器，
 * 工作进程级可以使用IP+Path来区分工作进程。如果工作机器比较少，可以使用配置文件来设置这个id是一个不错的选择，
 * 如果机器过多配置文件的维护是一个灾难性的事情
 * <p>
 * <p>
 * Snowflake – 序列号
 * 序列号就是一系列的自增id（多线程建议使用atomic），
 * 为了处理在同一毫秒内需要给多条消息分配id，若同一毫秒把序列号用完了，则“等待至下一毫秒”。
 */

public class SnowflakeServer {
    // ==============================Fields===========================================
    /**
     * 开始时间截 (2015-01-01)
     */
    private long twepoch = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private long workerIdBits = 10L;


    /**
     * 支持的最大机器id
     */
    private long maxWorkerId;


    /**
     * 序列在id中占的位数
     */
    private long sequenceBits = 12L;


    /**
     * 机器ID的移位
     */
    private long workerIdShift;


    /**
     * 时间截的位移
     */
    private long timestampLeftShift;

    /**
     * 生成序列的掩码
     */
    private long sequenceMask;

    /**
     * 工作机器ID(0~31)
     */
    private long workerId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;

    private long timestampBits = 41;

    private long maxTimestamp;

    private long maxDeltaTimestammp;
    //==============================Constructors=====================================
//    private static Logger logger = LoggerFactory.getLogger(SnowflakeIdUtils.class);



    /**
     * 构造函数
     */
    public SnowflakeServer(SnowflakeProperties properties,IdWorker idWorker) {
        if (properties.getWorkerBits() > 0) {
            this.workerIdBits = properties.getWorkerBits();
        }
        if (properties.getSequenceBits() > 0) {
            this.sequenceBits = properties.getSequenceBits();
        }
        this.maxWorkerId = ~(-1L << workerIdBits);


        long initWorkerId = idWorker.initWorkerId();
        if (initWorkerId > maxWorkerId || initWorkerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }

        this.workerId = initWorkerId;

        this.workerIdShift = sequenceBits;
        this.timestampLeftShift = sequenceBits + workerIdBits;
        this.sequenceMask = ~(-1L << sequenceBits);
        this.maxTimestamp = ~(-1L << timestampBits);
        this.maxDeltaTimestammp = maxTimestamp - twepoch;
    }

    // ==============================Methods==========================================

   public synchronized String nextPrefixId(String prefix){
        return prefix+nextId();
   }

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变，毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift)
                | (workerId << workerIdShift)
                | sequence;
    }


    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    private long timeGen() {
        long currentTimeMillis = System.currentTimeMillis();
        if (currentTimeMillis - twepoch > maxDeltaTimestammp) {
            throw new RuntimeException("Timestamp bits is exhausted. Refusing  generate. Now: " + currentTimeMillis);
        }
        return currentTimeMillis;
    }


    //==============================test=============================================

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {
        int size = 10000;
        Set<Long> set = new HashSet<>();
        int threadSize = 5;
        long start = System.currentTimeMillis();
        List<Thread> threads = new LinkedList<>();
        SnowflakeProperties snowflakeProperties = new SnowflakeProperties();
        snowflakeProperties.setWorkerBits(2);
        snowflakeProperties.setSequenceBits(4);
        SnowflakeServer idWorker = new SnowflakeServer(snowflakeProperties,null);
        for (int i = 0; i < threadSize; i++) {
            Thread t = new Thread(() -> {
                for (int j = 0; j < size; j++) {
                    Long id = idWorker.nextId();
                    synchronized (SnowflakeServer.class) {
                        set.add(id);
                    }
                }
            });
            threads.add(t);
            t.start();
        }

        for (Thread thread : threads) {
            thread.join();
        }


        long end = System.currentTimeMillis();

        System.out.println("set size " + set.size());
        System.out.println("used time" + (end - start));
    }


}

